package models

import (
	"gitchina/kly_service/data"
	"gitchina/kly_service/errors"
	"gitchina/kly_service/protobuf"
	"gitchina/kly_service/util"

	"github.com/astaxie/beego"
	"github.com/golang/protobuf/proto"
	"github.com/pborman/uuid"
	"time"
)

var redis util.Redis
var safemap = util.NewSafeMap()

type Auth struct{}

type LoginBlock struct {
	Count    int
	ExpireAt time.Time
}

type Session struct {
	Expire     int64
	MemberInfo MemberRS
}

func AuthInit() {
	redis = util.NewRedis(beego.AppConfig.DefaultString("redisServer", "120.26.223.135:22359"))
}

type LoginRQ struct {
	Phone      string `json:"phone"`
	Passwd     string `json:"password"`
	Platform   string `json:"platform"`
	RegisterId string `json:"register_id"`
}

func (a Auth) Login(phone, passwd, registerId, platform, loginIp string) (mbrInfo MemberRS, err error) {
	var pd []byte
	var mbrDto data.MemberDto
	var r data.MemberRepo

	v, ok := safemap.Get(loginIp)
	if ok {
		block := v.(LoginBlock)
		if block.Count >= 3 {
			if !time.Now().After(block.ExpireAt) {
				err = errors.New(errors.MSG_Mbr_Too_Many_Login_Failed)
				return
			} else {
				safemap.Remove(loginIp)
			}
		}
	}

	if mbrDto, err = r.CheckPhoneAndPasswd(phone, passwd); err == nil {
		var mbrId ID
		var vid ID
		mbrId.Encode(ID_Shard, ID_Ver, ID_Member_Category, int64(mbrDto.Id))
		vid.Encode(ID_Shard, ID_Ver, ID_Member_Category, int64(mbrDto.Inviter))
		pm := protobuf.Member{
			Id:           string(mbrId),
			RawId:        int32(mbrDto.Id),
			Name:         mbrDto.Name,
			NickName:     mbrDto.NickName,
			Phone:        mbrDto.Phone,
			MemberType:   int32(mbrDto.TypeId),
			State:        int32(mbrDto.State),
			Level:        int32(mbrDto.Level),
			AuthLevel:    int32(mbrDto.AuthLevel),
			IDCardNo:     mbrDto.IDCardNo,
			QRCodeUrl:    mbrDto.QRCode,
			PhotoUrl:     mbrDto.PhotoUrl,
			Token:        uuid.New(),
			InviterID:    string(vid),
			InviteCode:   string(mbrDto.Phone),
			QRCodeExists: len(mbrDto.QRCode) > 0,
		}
		if pd, err = proto.Marshal(&pm); err == nil {
			a.ensureOneLogin(pm.Phone)
			expire := 3600 * beego.AppConfig.DefaultInt("tokenExpire", 72)
			if resp := redis.Cmd("set", pm.Token, pd, "ex", expire); resp.Err != nil {
				util.GetLogger().Error("redis call error:%s", resp.Err.Error())
			}
			if resp := redis.Cmd("set", pm.Phone, pm.Token, "ex", expire); resp.Err != nil {
				util.GetLogger().Error("redis call error:%s", resp.Err.Error())
			}
			mbrInfo = MemberRS{
				Id:           ID(pm.Id),
				RawId:        int(pm.RawId),
				Name:         pm.Name,
				NickName:     pm.NickName,
				Phone:        pm.Phone,
				MemberType:   int(pm.MemberType),
				State:        int(pm.State),
				Level:        int(pm.Level),
				AuthLevel:    int(pm.AuthLevel),
				IDCardNo:     pm.IDCardNo,
				QRCodeUrl:    pm.QRCodeUrl,
				PhotoUrl:     pm.PhotoUrl,
				InviterID:    pm.InviterID,
				InviteCode:   pm.InviteCode,
				Token:        pm.Token,
				QRCodeExists: pm.QRCodeExists,
			}
			jms := util.NewJMessageClient()
			if exists, err := jms.UserExists(string(mbrId)); err == nil && !exists {
				jms.RegisterUser(string(mbrId), passwd)
			}
			r.AddLoginLog(mbrInfo.RawId, 1, registerId, platform)
		} else {
			err = errors.New(errors.MSG_Mbr_Invalid_Pwd_Or_Phone)
		}
		safemap.Remove(loginIp)
	} else {
		util.GetLogger().Error("[model] - login error: %s", err.Error())

		v, ok := safemap.Get(loginIp)
		if !ok {
			safemap.Set(loginIp, LoginBlock{
				Count: 1,
			})
		} else {
			block := v.(LoginBlock)
			block.Count = block.Count + 1
			if block.Count >= 3 {
				block.ExpireAt = time.Now().Add(time.Minute * 30)
			}
			safemap.Set(loginIp, block)
		}

		err = errors.New(errors.MSG_Mbr_Invalid_Pwd_Or_Phone)
	}

	return
}

func (a Auth) Authenticate(token string) (pass bool, mbrId, phone, nickName string) {
	if resp := redis.Cmd("get", token); resp.Err == nil {
		var pm protobuf.Member
		if p, err := resp.Bytes(); err == nil {
			if err := proto.Unmarshal(p, &pm); err == nil {
				pass = true
				mbrId = pm.Id    // string(session.MemberInfo.Id)
				phone = pm.Phone // session.MemberInfo.Phone
				nickName = pm.NickName
			} else {
				util.GetLogger().Error("invalid member info token:%s", token)
			}
		} else {
			util.GetLogger().Error("auth token:%s not found", token)
		}
	} else {
		util.GetLogger().Error("verify login state error:%s, token=%s", resp.Err.Error(), token)
	}
	return
}

func (a Auth) Logout(token string) {
	if resp := redis.Cmd("get", token); resp.Err == nil {
		var pm protobuf.Member
		if p, err := resp.Bytes(); err == nil {
			if err := proto.Unmarshal(p, &pm); err == nil {
				redis.Cmd("del", token)
				redis.Cmd("del", pm.Phone)
				memberId := ID(pm.Id)
				if _, _, _, id, err := memberId.Decode(); err == nil {
					var r data.MemberRepo
					r.AddLoginLog(int(id), 2, "", "")
				}
			}
		}
	}
}

func (a Auth) ensureOneLogin(phone string) {
	if resp := redis.Cmd("get", phone); resp.Err == nil {
		if token, err := resp.Str(); err == nil {
			redis.Cmd("del", token, phone)
		}
	}
}
